# -*- coding: utf-8 -*-
# SSHexec 工具文件

# 系统或第三方模块
import os
import re
import sys
import time
from loguru import logger
from datetime import datetime
from posixpath import join as posix_join
from functools import wraps


# 自定义模块
import src.config as config

# -*- coding: utf-8 -*-
# SSHexec 工具文件

# 系统或第三方模块
import os
import sys
from loguru import logger
from functools import wraps

# 自定义模块
import src.config as config

# 初始化全局logger变量
tlog = logger.bind(logger_type="tool")
elog = logger.bind(logger_type="exec")


class JumpOut(Exception):
    """自定义异常，用于跳出try，中断执行"""
    pass


def init_tool_logger(log_dir: str):
    global tlog
    
    try:
        os.makedirs(log_dir, exist_ok=True)
        
        # 移除默认handler
        logger.remove()
        
        # 添加tool日志handler
        tlog.add(
            os.path.join(log_dir, config.DEFAULT_LOG_TOOL),
            rotation="50 MB",
            level="DEBUG",
            encoding="utf-8",
            format="{time:YYYY-MM-DD HH:mm:ss.SSS} - [{level}] - {message}",
            filter=lambda record: record["extra"].get("logger_type") == "tool"
        )
        return tlog
        
    except Exception as e:
        print_error_informantion_and_exit("init_tool_logger", f"初始化工具日志失败: {str(e)}")

def init_execution_logger(log_dir: str) :
    global elog
    
    try:
        os.makedirs(log_dir, exist_ok=True)
        
        # 添加execution日志handler
        elog.add(
            os.path.join(log_dir, config.DEFAULT_LOG_EXEC),
            level="DEBUG",
            enqueue=True,  # 启用线程安全队列
            encoding="utf-8",
            format="{time:YYYY-MM-DD HH:mm:ss.SSS} - [{level}] - {message}",
            filter=lambda record: record["extra"].get("logger_type") == "exec"
        )
        return elog
        
    except Exception as e:
        print_error_informantion_and_exit("init_execution_logger", f"初始化执行日志失败: {str(e)}")

def get_user_confirmation(prompt, yorn=False):
    """
    功能：
        获取用户确认的通用函数

    参数：
        prompt: 确认提示信息
        yorn: 是否为 yes/no 确认，默认 False

    返回：
        confirm: 用户确认结果，True 或 False
    """

    try:
        if yorn:
            confirm = input(f"{prompt} {config.COLOR_RED}[Y/n]{config.COLOR_RESET}: ").strip().lower() or 'y'
        else:
            confirm = input(f"{prompt} {config.COLOR_RED}[y/N]{config.COLOR_RESET}: ").strip().lower() or 'n'
        return confirm == 'y'
    except KeyboardInterrupt:
        print(f"\n{config.COLOR_YELLOW}操作已取消{config.COLOR_RESET}")
        sys.exit(1)
    except EOFError:  # 处理管道输入等情况
        print(f"\n{config.COLOR_YELLOW}输入结束，操作已取消{config.COLOR_RESET}")
        sys.exit(1)


def remove_command_fist_last_same_symbol(cmd_str):
    """
    功能：
        去除 command 命令 首尾相同的特殊符号

    参数：
        cmd_str: 命令字符串

    返回：
        removed_symbol: 被移除的特殊符号
        cmd_str: 处理后的命令字符串
    """


    # 特殊符号黑名单，以下符号不移除
    forbidden_chars = r'^$*+?.()[]{}|\/'

    # 判断并处理 , 命令大于一个字符、首尾相同、首尾不是字母或数字、首尾不在特殊符号黑名单中
    if len(cmd_str) > 1 and cmd_str[0] == cmd_str[-1] and not cmd_str[0].isalnum() and cmd_str[0] not in forbidden_chars:
        
        removed_symbol = cmd_str[0]  # 记录被移除的符号
        cmd_str = cmd_str[1:-1]     # 实际移除操作
        return removed_symbol, cmd_str
    else:
        return None, cmd_str




def create_latest_log_symlink():
    """
    功能：
        创建最新日志符号链接

    参数：
        None

    返回：
        None
    """

    # 检查一下当前系统环境
    if os.name != 'posix':
        tlog.warning("当前系统环境不是POSIX兼容系统，无法创建符号链接")
        return

    if not os.path.isdir(config.DEFAULT_DIR_NAME_HISTORYS):
        print("错误: 历史记录目录 (historys) 不存在")
        tlog.error("历史记录目录 (historys) 不存在")
        return

    # 获取historys目录下所有子目录，并按创建时间倒序排序
    log_dirs = []
    for entry in os.scandir(config.DEFAULT_DIR_NAME_HISTORYS):
        if entry.is_dir():
            log_dirs.append(entry)
    # 按创建时间排序，最新在前
    log_dirs.sort(key=lambda x: x.stat().st_ctime, reverse=True)

    if not log_dirs:
        print(f"{config.COLOR_RED}[ERROR]{config.COLOR_RESET}{config.COLOR_YELLOW}[function:package_latest_history]{config.COLOR_RESET} 历史记录目录 '{config.DEFAULT_DIR_NAME_HISTORYS}' 中没有日志文件夹", file=sys.stderr)
        print("提示: 请先至少一次执行任务以生成历史记录")
        tlog.error("历史记录目录 (historys) 中没有日志文件夹")
        return

    latest_log_dir = posix_join(config.DEFAULT_DIR_NAME_HISTORYS, log_dirs[0].name)
    latest_link = 'latest_history'

    try:
        if os.path.exists(latest_link):
            if os.path.islink(latest_link):
                os.remove(latest_link)
            else:
                print(f"警告: 已存在同名文件 {latest_link}，无法创建符号链接")
                tlog.warning(f"已存在同名文件 {latest_link}，无法创建符号链接")
                return
        os.symlink(latest_log_dir, latest_link)
        tlog.success(f"创建最新日志符号链接函数执行成功，指向路径: {latest_log_dir}")
    except OSError as e:
        print(f"创建符号链接失败: {str(e)}")
        tlog.error(f"创建最新日志符号链接函数执行失败\n异常类型: {type(e)} \n异常参数: {e.args} \n异常信息: {e}")
        if e.errno == 1:
            print("提示: 请尝试使用管理员/root权限运行")
            tlog.error("创建最新日志符号链接函数执行失败，权限不足")



def error_classify(ip: str, error_text: str) -> str:
    """
    功能：
        根据错误文本内容进行分类

    参数：
        ip: 设备IP
        error_text: 错误文本内容
        
    返回：
        错误类型分类
    """
    import src.dict as dict
    
    # 转换为小写进行匹配
    error_lower = error_text.lower()
    elog.info(f"{ip}：错误类型原始文本: {error_text}")
    # 注意：这里的关键词是英文，字典关键词全为小写
    for category, keywords in dict.ERROR_CATEGORIES.items():
        # 获取单个中文的多个关键词
        for keyword in keywords:
            # 检查关键词是否在错误文本中
            if keyword in error_lower:
                return category
                
    elog.error(f"{ip}：未匹配到错误类型")
    return "错误未分类"


def create_exec_log_dir(args) -> str:
    """
    功能：
        创建日志目录

    参数：
        args: 命令行参数
        
    返回：
        日志目录路径
    """
    
    # 使用可读的日期时间格式，而不是时间戳
    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    
    if args.c:
        file_name = "command"
    elif args.s:
        file_name = "script"
    elif args.u:
        file_name = "upload"
    elif args.d:
        file_name = "download"
    else:
        file_name = "unknown"  # 添加默认值避免未定义

    # 最后拼接成的大概路径样子是 history/2025-08-20_12-12-12_command/
    log_dir = posix_join(config.DEFAULT_DIR_NAME_HISTORYS, f"{timestamp}_{file_name}")
    
    if args.r:
        log_dir = log_dir + f"_{args.r.strip()}"

    # 生成日志目录（创建完整路径）
    os.makedirs(log_dir, exist_ok=True)

    return log_dir


def print_error_informantion_and_exit( func_name: str, error_str: str ):
    """
    功能：
        打印错误信息并退出程序

    参数：
        func_name: 函数名
        error_str: 错误信息
        
    返回：
        None
    """
    print(f"{config.COLOR_RED}[ERROR]{config.COLOR_RESET}{config.COLOR_YELLOW}[function:{func_name}]{config.COLOR_RESET} {error_str}", file=sys.stderr)
    sys.exit(1)


def args_normalize_path(path):
    """
    功能：
        纯字符串层面的路径规范化处理（不涉及实际文件系统）

    规则：
      - 统一分隔符为 `/`
      - 处理 `.` 和 `..`（仅字符串层面）
      - 不强制添加或删除结尾 `/`
      - 保留原始路径类型（绝对/相对）
      - Windows 盘符转为 `C:/` 格式

    参数：
        path: 待处理的路径字符串

    返回：
        处理后的路径字符串
    """

    path = str(path).strip()

    # 统一转换分隔符（\ → /）
    path = path.replace("\\", "/")

    # 记录是否以 / 结尾
    ends_with_slash = path.endswith("/")

    # 处理 Windows 盘符路径（如 C:\ → C:/）
    if re.match(r'^[A-Za-z]:', path):
        drive = path[0].upper()
        rest = path[2:].lstrip("/")
        path = f"{drive}:/{rest}" if rest else f"{drive}:/"
        return path  # Windows 路径不处理结尾 /

    # 使用 normpath 处理 . 和 ..（但会去掉结尾 /）
    path = os.path.normpath(path).replace("\\", "/")

    # 还原用户输入的结尾 /
    if ends_with_slash and path != "/":
        path += "/"

    return path


# 测试函数执行耗时的装饰器
def time_decorator(func):
    """
    功能：
        统计函数执行耗时
    
    参数：
        func: 被装饰的函数
        
    返回：
        被装饰函数的返回值
    """
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        func_cost_time = round(end_time -start_time, 3)
        print(f"{config.COLOR_RED}函数 {func.__name__} 执行耗时: {func_cost_time} 秒{config.COLOR_RESET}")
        return result
    return wrapper


def time_imports(func):
    """
    功能：
        统计导入模块的耗时
    
    参数：
        func: 被装饰的函数

    返回：
        被装饰函数的返回值
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        original_import = __import__

        def timed_import(name, *args, **kwargs):
            start_time = time.perf_counter()
            module = original_import(name, *args, **kwargs)
            end_time = time.perf_counter()
            print(f"导入模块 {name} 耗时: {end_time - start_time:.6f} 秒")
            return module

        # 临时替换 __import__ 以计时
        builtins = __import__("builtins")
        builtins.__import__ = timed_import

        try:
            result = func(*args, **kwargs)
        finally:
            # 恢复原始 __import__
            builtins.__import__ = original_import

        return result
    return wrapper




def func_cost_time(func):
    """
    函数装饰器，为函数添加执行前后时间耗时计算，使用datetime获取时间
    Args:
        func: 被装饰的函数
    Returns:
        wrapper: 包装后的函数
    """
    from functools import wraps
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = datetime.now()
        result = func(*args, **kwargs)
        end_time = datetime.now()
        # 计算耗时
        cost_time = end_time - start_time
        # 转换为毫秒
        cost_time_ms = cost_time.total_seconds() * 1000
        print(f"{config.COLOR_GREEN}[DEBUG]{config.COLOR_RESET}{config.COLOR_YELLOW}[function:{func.__name__}]{config.COLOR_RESET} 执行耗时: {cost_time_ms:.2f}ms.")
        return result
    return wrapper
